/*
 * *****************************************************************************
 * Copyright (C) 2014-2024 Dennis Sheirer
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 * ****************************************************************************
 */

package io.github.dsheirer.edac;

import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Galois 24/12/7 decoder
 */
public class Golay24
{
    private final static Logger mLog = LoggerFactory.getLogger(Golay24.class);

    /**
     * Galois 24/12 checksums generated by:
     *
     * CRCUtil.generate( 12, 11, 0xC75, 0x0, true );
     */
    public static final int[] CHECKSUMS = new int[]
            {
                    0x63A, 0x31D, 0x7B4, 0x3DA, 0x1ED, 0x6CC, 0x366, 0x1B3,
                    0x6E3, 0x54B, 0x49F, 0x475, 0x400, 0x200, 0x100, 0x080,
                    0x040, 0x020, 0x010, 0x008, 0x004, 0x002, 0x001
            };

    private static int calculateChecksum(BinaryMessage message, int startIndex)
    {
        int calculated = 0; //Starting value

        /* Iterate the set bits and XOR running checksum with lookup value */
        for(int i = message.nextSetBit(startIndex);
            i >= startIndex && i < startIndex + 12;
            i = message.nextSetBit(i + 1))
        {
            calculated ^= CHECKSUMS[i - startIndex];
        }

        return calculated;
    }

    /**
     * Performs error detection and returns a corrected copy of the 24-bit
     * message that starts at the start index.
     *
     * @param message - source message containing startIndex + 24 bits length
     * @param startIndex - start of the 24-bit galois 24 protected bit set
     * @return - corrected 24-bit galois value
     */
    public static int checkAndCorrect(CorrectedBinaryMessage message, int startIndex)
    {
        boolean parityError = message.cardinality() % 2 != 0;

        int syndrome = getSyndrome(message, startIndex);

        /* No errors */
        if(syndrome == 0)
        {
            if(parityError)
            {
                message.flip(startIndex + 23);
                message.incrementCorrectedBitCount(1);
                return 1;
            }

            return 0;
        }

        /* Get original message value */
        int original = message.getInt(0, 22);

        int index = -1;
        int syndromeWeight = 3;
        int errors = 0;

        while(index < 23)
        {
            if(index != -1)
            {
                /* restore the previous flipped bit */
                if(index > 0)
                {
                    message.flip(index - 1);
                }

                message.flip(index);

                syndromeWeight = 2;
            }

            syndrome = getSyndrome(message, startIndex);

            if(syndrome > 0)
            {
                for(int i = 0; i < 23; i++)
                {

                    errors = Integer.bitCount(syndrome);

                    if(errors <= syndromeWeight)
                    {
                        message.xor(12, 11, syndrome);

                        message.rotateRight(i, startIndex, startIndex + 22);

                        if(index >= 0)
                        {
                            errors++;
                        }

                        int corrected = message.getInt(0, 22);

                        if(Integer.bitCount(original ^ corrected) > 3)
                        {
                            return 2;
                        }

                        return 1;
                    }
                    else
                    {
                        message.rotateLeft(startIndex, startIndex + 22);
                        syndrome = getSyndrome(message, startIndex);
                    }
                }

                index++;
            }
        }

        return 2;
    }

    private static int getSyndrome(BinaryMessage message, int startIndex)
    {
        int calculated = calculateChecksum(message, startIndex);

        int checksum = message.getInt(startIndex + 12, startIndex + 22);

        return (checksum ^ calculated);
    }

    public static void main(String[] args)
    {
//        CorrectedBinaryMessage bm = new CorrectedBinaryMessage(BinaryMessage.loadHex("F3BB20"));
//        CorrectedBinaryMessage bm = new CorrectedBinaryMessage(BinaryMessage.loadHex("F0C5C0"));
        CorrectedBinaryMessage bm = new CorrectedBinaryMessage(BinaryMessage.loadHex("AFAC00"));

        System.out.println("M:" + bm.toHexString());
        int a = Golay24.checkAndCorrect(bm, 0);
        System.out.println("M:" + bm.toHexString());

        System.out.println("A:" + a);
    }
}
